﻿namespace Legend.Core.Tests.Caching.InMemoryCacheTests
{
    using System;
    using Legend.Caching;
    using Legend.Core.Tests.Caching.PurgedInMemoryCacheTests;
    using NUnit.Framework;

    public class ObjectCacheAdaptorTests
       : CacheTests
    {
        protected override ICache CreateCache()
        {
            return new ObjectCacheAdaptor();
        }
    }

    public abstract class ObjectCacheAdaptorFixture
    {
        protected ObjectCacheAdaptor cache;
        protected SystemTimeProvider systemTime;
        protected DateTime currentTime;

        [SetUp]
        public void SetUp()
        {
            this.OnSetUp();
        }

        protected virtual void OnSetUp()
        {
            currentTime = new DateTime();
            systemTime = () => currentTime;

            this.cache = new ObjectCacheAdaptor();
        }
    }

    [TestFixture]
    public class Insert
        : ObjectCacheAdaptorFixture
    {
        [Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void should_throw_exception_when_timeToLive_is_negative()
        {
            this.cache.Insert("foo", "bar", TimeSpan.FromMinutes(-1));
        }

        [Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void should_throw_exception_when_timeToLive_is_zero()
        {
            this.cache.Insert("foo", "bar", TimeSpan.FromTicks(0));
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_key_is_null()
        {
            this.cache.Insert(null, "bar", TimeSpan.FromSeconds(1));
        }

        [Test]
        public void should_get_value_when_inserted_and_timeToLive_has_not_passed()
        {
            this.cache.Insert("foo", "bar", TimeSpan.FromSeconds(1));
            
            object value = null;
            Assert.IsTrue(this.cache.TryGetValue("foo", out value));
        }

        [Test]
        public void should_get_value_when_inserted_and_time_to_live_ends_now()
        {
            this.cache.Insert("foo", "bar", TimeSpan.FromSeconds(1));

            currentTime = currentTime.AddSeconds(1);
            object value = null;
            Assert.IsTrue(this.cache.TryGetValue("foo", out value));
        }
    }

    [TestFixture]
    public class TryGetValue
        : ObjectCacheAdaptorFixture
    {
        object value;

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_key_is_null()
        {
            this.cache.TryGetValue(null, out value);
        }

        [Test]
        public void should_set_value_to_the_out_variable()
        {
            this.cache.Insert("foo", "bar", TimeSpan.FromMinutes(1));
            this.cache.TryGetValue("foo", out value);

            Assert.AreEqual("bar", value);
        }

        [Test]
        public void should_not_set_value_when_key_does_not_exist()
        {
            this.cache.TryGetValue("foo", out value);

            Assert.IsNull(value);
        }
    }

    [TestFixture]
    public class Remove
        : ObjectCacheAdaptorFixture 
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_key_is_null()
        {
            cache.Remove(null);
        }

        [Test]
        public void should_remove_value_from_cache()
        {
            cache.Insert("foo", "bar", TimeSpan.FromSeconds(1));

            cache.Remove("foo");

            object value;
            Assert.IsFalse(cache.TryGetValue("foo", out value));
        }
    }
}
